package org.zhaya.sdk.processors;

import cn.hutool.core.util.StrUtil;
import com.google.common.base.Enums;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import io.logz.sawmill.Doc;
import io.logz.sawmill.ProcessResult;
import io.logz.sawmill.Processor;
import io.logz.sawmill.annotations.ProcessorProvider;
import io.logz.sawmill.exceptions.ProcessorConfigurationException;
import io.logz.sawmill.parser.ConditionDefinition;
import io.logz.sawmill.parser.ConditionDefinitionParser;
import io.logz.sawmill.parser.ConditionParser;
import io.logz.sawmill.utilities.JsonUtils;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.zhaya.sdk.DodpProcessor;
import org.zhaya.sdk.constant.OperatorEnum;
import org.zhaya.sdk.processors.condition.ProcessorEnum;
import org.zhaya.sdk.swamill.FactoriesRegister;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@ProcessorProvider(type = "dataFilter", factory = FilterProcessor.Factory.class)
public class FilterProcessor implements Processor {

    private final List<FilterCondition> conditionDefinitionMapList;
    private final ConditionParser parser;

    public FilterProcessor(List<FilterCondition> conditionDefinitionMapList) {
        this.conditionDefinitionMapList = conditionDefinitionMapList;
        this.parser = new ConditionParser(FactoriesRegister.conditionFactoryRegistry);
    }

    @Override
    public ProcessResult process(Doc doc) {
        for (FilterCondition map : conditionDefinitionMapList) {
            boolean evaluate;
            Map<String, Object> conditionDefinition = map.getMap();
            if (MapUtils.isNotEmpty(conditionDefinition)) {
                io.logz.sawmill.Condition.Factory factory = FactoriesRegister.conditionFactoryRegistry.get((String) conditionDefinition.get(ProcessorEnum.type.name()));
                io.logz.sawmill.Condition condition = factory.create(conditionDefinition, parser);
                evaluate = condition.evaluate(doc);
            } else {
                evaluate = true;
            }

            if (evaluate) {
                if (map.isDrop()) {
                    return ProcessResult.drop();
                } else {
                    return ProcessResult.success();
                }
            }
        }
        return ProcessResult.success();
    }

    public static class Factory implements DodpProcessor.Factory {
        @Override
        public Processor create(Map<String, Object> config) {

            List<Map<String, Object>> criterias = (List) config.get(ProcessorEnum.criteria.name());

            List<FilterCondition> conditionMapList = Lists.newArrayListWithCapacity(criterias.size());
            for (Map<String, Object> criteria : criterias) {
                String logic = criteria.get(ProcessorEnum.logic.name()).toString();
                Boolean drop = (Boolean) criteria.get(ProcessorEnum.drop.name());

                List<Map<String, Object>> conditions = (List<Map<String, Object>>) criteria.get(ProcessorEnum.condition.name());
                if (CollectionUtils.isEmpty(conditions) && ProcessorEnum.and.name().equals(logic)) {
                    conditionMapList.add(FilterCondition.builder().drop(drop).build());
                } else {
                    List<Map<String, Object>> compares = Lists.newArrayList();
                    for (Map<String, Object> condition : conditions) {
                        String operator = condition.get(ProcessorEnum.operator.name()).toString();
                        String field = condition.get(ProcessorEnum.field.name()).toString();
                        Object value = condition.get(ProcessorEnum.value.name());
                        Map<String, Object> transfer = Enums.getIfPresent(OperatorEnum.class, operator)
                                .toJavaUtil().orElseThrow(() -> new ProcessorConfigurationException("failed to covert condition enum, please check the operator: {" + operator + StrUtil.DELIM_END))
                                .transfer(field, value);
                        compares.add(transfer);
                    }
                    Map<String, Object> conditionMap = Maps.newHashMap();
                    conditionMap.put(logic, compares);
                    List<Map<String, Object>> configList = JsonUtils.getList(conditionMap, logic, true);
                    List<ConditionDefinition> conditionDefinitionList = configList.stream().map(ConditionDefinitionParser::parse).collect(Collectors.toList());

                    Map<String, Object> conditionDefinition = Maps.newHashMapWithExpectedSize(2);
                    conditionDefinition.put(ProcessorEnum.type.name(), logic);
                    conditionDefinition.put(ProcessorEnum.conditions.name(), conditionDefinitionList);

                    FilterCondition filterCondition = FilterCondition.builder().map(CollectionUtils.isEmpty(compares) ? null : conditionDefinition)
                            .drop(drop).build();
                    conditionMapList.add(filterCondition);
                }
            }
            return new FilterProcessor(conditionMapList);
        }
    }

    @Data
    @Builder
    public static class Configuration implements Processor.Configuration {
        private final List<Criteria> criteria;
    }

    @Data
    @Builder
    public static class Criteria {
        private final String logic;
        private final List<Condition> condition;
        private final boolean drop;
    }

    @Data
    @Builder
    public static class Condition {
        private final String operator;
        private final String field;
        private final Object value;
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @SuperBuilder
    public static class FilterCondition {
        private Map<String, Object> map;
        private boolean drop;
    }

}